local cjson             = require("cjson.safe")
local httpc             = require("http.httpc")
local json_encode       = require( "json_encode")
local md5               = require "md5"
local setmetatable      = setmetatable
local decode_json       = cjson.decode
local encode_json       = json_encode.json_encode_table

local _M = {}
local api_prefix="/api/storage/"
local mt = {__index = _M}
local os_time=os._time or os.time
local table_exist_keys = function (t)
    return next(t)
end

local function urlencode(s)
    return (string.gsub(s, "([^A-Za-z0-9_])", function(c)
        return string.format("%%%02X", string.byte(c))
    end))
end

local function tbl_iterator(tbl,cmp)
	local a = {}  
	for n in pairs(tbl) do  
		a[#a+1] = n  
	end  
	table.sort(a,cmp) 
	local i = 0  
	return function()  
		i = i + 1  
		return a[i], tbl[a[i]]  
	end 
end

local function encode_args(params)
    local str = ''
    local is_not_first = false
    for k,v in tbl_iterator(params) do
        local s=urlencode(k) .. '=' .. urlencode(v)
        if is_not_first then
            str = str .. '&' ..s
        else
            str = str .. s
            is_not_first = true
        end
    end
    return str
end
local function verify_key(key)
    if type(key) ~= "string" then
        return false, 'key must be string'
    end
    if not key or #key == 0 then
        return false, "key should not be empty"
    end

    return true, nil
end

local function _request_uri(self, method, uri, opts)
    local query=opts.query or {}
    if query.timestamp == nil  then
        query.timestamp=os_time()
    end
    local body
    if opts and opts.body and table_exist_keys(opts.body) then
        body = encode_json(opts.body)
    end
    local args=encode_args(query)
    uri = uri .. '?' ..args.."&sign="..md5.sumhexa(args..self.secret)
    local headers = {}
    local status, res = httpc.request(method, self.host, uri, {}, headers, body)
    if status ~= 200 then
        error("invalid response code: " .. status)
        return
    end
    if type(res) ~="string" then
        error("invalid response type")
        return
    end
    local obj=decode_json(res)
    if obj.code ~= 0 then
        return  nil,obj.message
    end
    return obj.data or true,obj.rev or 0
end

function _M.new(opts)
    local host   = opts.host
    local secret = opts.secret


    if type(host) ~="string" or type(secret) ~= "string" then
        return nil, 'opts.host and opts.secret  must be string'
    end

    return setmetatable({
        secret = secret,
        host = host
    },mt)
end

function _M.query(self,cmd,query)
    if query.key then
        local _, err = verify_key(query.key)
        if err then
            return nil, err
        end
    end
    return _request_uri(self, 'GET',api_prefix..cmd, {query=query})
end

function _M.set(self, key, val, ttl)
    -- verify key
    local _, err = verify_key(key)
    if err then
        return nil, err
    end
    if type(val)=="table"  then
        val=encode_json(val)
    end
    local opts = {
        body = {
            value = val,
            key = key,
            ttl = ttl,
        }
    }
    return _request_uri(self, 'POST',api_prefix.."set", opts)
end

return _M
